/////////////////////////////////////////////////////////////////////////////
// Name:        ax_speech.h
// Purpose:     Speech class
// Author:      Julian Smart
// Modified by:
// Created:     2009-03-20
// RCS-ID:
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"
#include "wx/confbase.h"

#include "ax/ax_speech.h"
#include "ax/ax_menu.h"

#include "tts/tts_espeak.h"
#include "tts/tts_cepstral.h"

#ifdef __WXMSW__
#include "tts/tts_sapi.h"
#endif

#ifdef __WXMAC__
#include "tts/tts_macosx.h"
#endif

IMPLEMENT_CLASS( AxSpeech, wxEvtHandler )
IMPLEMENT_CLASS( AxSpeechEventHandler, wxEvtHandler )
IMPLEMENT_DYNAMIC_CLASS( AxStandardSpeechEventHandler, AxSpeechEventHandler )

AxSpeechInfo::AxSpeechInfo()
{
    m_freezeCount = 0;
    m_speechCounter = 0;
}

AxSpeech::AxSpeech()
{
    Init();

    m_transformer = new wxTTSTransformer;

    GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.SetTransformer(m_transformer);
    GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.SetTransformer(m_transformer);

    Push(new AxStandardSpeechEventHandler);

    InitialiseStandardSpeechHandlers();
}

AxSpeech::~AxSpeech()
{
    ClearSpeechEvents();

    AxSpeechChannel channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        AxSpeechInfo& info = GetChannelInfo(channel);
        if (info.m_textToSpeech.IsSpeaking() || info.m_textToSpeech.IsPaused())
            info.m_textToSpeech.Stop();
    }

    while (Pop())
    {
    }

    SetTransformer(NULL);
}

void AxSpeech::Init()
{
    m_handler = NULL;
    m_transformer = NULL;
}

bool AxSpeech::EmitSpeech(AxSpeechEvent& event)
{
    ScheduleSpeechEvent(event);
    return true;
}

// Say some text
bool AxSpeech::Say(const wxString& text, int flags, int verbosity)
{
    AxSpeechEvent speechEvent;
    speechEvent.SetSpeechEventType(AxSpeechEventText);
    speechEvent.SetVerbosityLevel(verbosity);
    speechEvent.SetText(text);
    speechEvent.SetFlags(flags);
    EmitSpeech(speechEvent);

    return true;
}

bool AxSpeech::ProcessSpeechEvent(AxSpeechEvent& event)
{
    AxSpeechEventHandler* handler = m_handler;
    while (handler)
    {
        if (handler->ProcessSpeechEvent(this, event))
            return true;
        handler = handler->GetNextHandler();
    }
    return false;
}

// Stop speaking, first in the main channel. Call again if the
// content is also speaking.
bool AxSpeech::Stop()
{
    if (GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.IsSpeaking() || GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.IsPaused())
    {
        GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.Stop();
    }
    else if (GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.IsSpeaking() || GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.IsPaused())
    {
        GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.Stop();
    }

    return true;
}

// Stop speaking in the specified channel.
bool AxSpeech::Stop(AxSpeechChannel channel)
{
    if (GetChannelInfo(channel).m_textToSpeech.IsSpeaking() || GetChannelInfo(channel).m_textToSpeech.IsPaused())
    {
        GetChannelInfo(channel).m_textToSpeech.Stop();
    }

    return true;
}

// Pause or resume speaking on the specified channel.
bool AxSpeech::PauseOrResume(AxSpeechChannel channel)
{
    if (GetChannelInfo(channel).m_textToSpeech.IsPaused())
    {
        return GetChannelInfo(channel).m_textToSpeech.Resume();
    }
    else if (GetChannelInfo(channel).m_textToSpeech.IsSpeaking())
    {
        return GetChannelInfo(channel).m_textToSpeech.Pause();
    }
    else
        return false;
}

// Pause or resume speaking on relevant channel.
bool AxSpeech::PauseOrResume()
{
    if (GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.IsSpeaking())
    {
        return GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.Pause();
    }
    else if (GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.IsSpeaking())
    {
        return GetChannelInfo(AxSpeechMainChannel).m_textToSpeech.Pause();
    }
    else if (GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.IsSpeaking())
    {
        return GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.Pause();
    }
    else if (GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.IsSpeaking())
    {
        return GetChannelInfo(AxSpeechContentChannel).m_textToSpeech.Pause();
    }
    else
        return false;
}

// Pushes a handler on the front of the speech event handler stack
bool AxSpeech::Push(AxSpeechEventHandler* handler)
{
    handler->SetNextHandler(m_handler);
    m_handler = handler;

    return true;
}

// Pushes a handler on the end of the speech event handler stack
bool AxSpeech::PushEnd(AxSpeechEventHandler* handler)
{
    if (!m_handler)
    {
        m_handler = handler;
        return true;
    }

    AxSpeechEventHandler* h = m_handler;
    while (h)
    {
        if (h->GetNextHandler() == NULL)
        {
            h->SetNextHandler(handler);
            return true;
        }

        h = h->GetNextHandler();
    }
    return false;
}

// Pops a handler from the front of the speech event handler, and deletes it
bool AxSpeech::Pop()
{
    if (m_handler)
    {
        AxSpeechEventHandler* h = m_handler->GetNextHandler();
        delete m_handler;
        m_handler = h;

        return true;
    }
    else
        return false;
}

// Sets the transformer. You may want to create a custom transformer.
void AxSpeech::SetTransformer(wxTTSTransformer* transformer)
{
    if (m_transformer)
        delete m_transformer;
    m_transformer = transformer;

    AxSpeechChannel channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        AxSpeechInfo& info = GetChannelInfo(channel);
        info.m_textToSpeech.SetTransformer(transformer);
    }
}

// Initialise standard speech engine handlers
bool AxSpeech::InitialiseStandardSpeechHandlers()
{
    AxSpeechChannel channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        AxSpeechInfo& info = GetChannelInfo(channel);

        info.m_textToSpeech.AddHandler(new wxTTSEspeakHandler);
        info.m_textToSpeech.AddHandler(new wxTTSCepstralHandler);
#ifdef __WXMSW__
        info.m_textToSpeech.AddHandler(new wxTTSSapiHandler);
#endif
#ifdef __WXMAC__
        info.m_textToSpeech.AddHandler(new wxTTSMacOSXHandler);
#endif
        
        // Set a default engine
#ifdef __WXMSW__
        info.m_speechSettingsInfo.m_engine = wxT("Microsoft SAPI");
#elif defined(__WXMAC__)
        info.m_speechSettingsInfo.m_engine = wxT("Apple Speech Manager");
#else
        info.m_speechSettingsInfo.m_engine = wxT("eSpeak");
#endif
        
        info.m_speechSettingsInfo.m_speech = & info.m_textToSpeech;
        info.m_speechSettingsInfo.ApplySettings();
    }

    SetupMultipleVoices();

    return true;
}

// Load speech settings from config
bool AxSpeech::LoadSpeechSettings(wxConfigBase& config)
{
    AxSpeechChannel channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        wxString channelName;
        if (channel == AxSpeechMainChannel)
            channelName = wxT("SpeechMain");
        else
            channelName = wxT("SpeechContent");

        AxSpeechInfo& info = GetChannelInfo(channel);
        config.Read(channelName + wxT("/Speed"), & info.m_speechSettingsInfo.m_speed);
        config.Read(channelName + wxT("/Volume"), & info.m_speechSettingsInfo.m_volume);
        config.Read(channelName + wxT("/Pitch"), & info.m_speechSettingsInfo.m_pitch);
        config.Read(channelName + wxT("/Engine"), & info.m_speechSettingsInfo.m_engine);
        config.Read(channelName + wxT("/TestText"), & info.m_speechSettingsInfo.m_testText);
        
        size_t i;
        for (i = 0; i < (size_t) info.m_textToSpeech.GetHandlerCount(); i++)
        {
            wxTTSHandler* handler = info.m_textToSpeech.GetHandler(i);
            wxString voice, voiceVariant, programLocation, dataLocation;
            config.Read(channelName + wxT("/") + handler->GetId() + wxT("Voice"), & voice);
            config.Read(channelName + wxT("/") + handler->GetId() + wxT("VoiceVariant"), & voiceVariant);
            config.Read(channelName + wxT("/") + handler->GetId() + wxT("ProgramLocation"), & programLocation);
            config.Read(channelName + wxT("/") + handler->GetId() + wxT("DataLocation"), & dataLocation);
            
            if (!voice.IsEmpty())
                handler->SetVoice(voice);
            if (!voiceVariant.IsEmpty())
                handler->SetVoiceVariant(voiceVariant);
            if (!programLocation.IsEmpty())
                handler->SetProgramLocation(programLocation);
            if (!dataLocation.IsEmpty())
                handler->SetDataLocation(dataLocation);
        }
        info.m_speechSettingsInfo.ApplySettings();
    }

    // Don't allow the system to have multiple voices enabled if it's going to make
    // the application crash.
    if (GetPolicy().IsMultipleVoiceModeEnabled() && !AreVoicesCompatible())
        GetPolicy().EnableMultipleVoiceMode(false);
    
    SetupMultipleVoices();

    return true;
}

// Call this after a change in multiple voice mode
void AxSpeech::SetupMultipleVoices()
{
    // Don't allow the system to have multiple voices enabled if it's going to make
    // the application crash.
    if (GetPolicy().IsMultipleVoiceModeEnabled() && !AreVoicesCompatible())
        GetPolicy().EnableMultipleVoiceMode(false);

    // If multiple voices are enabled, we need to set the priority so the
    // voices can speak over each other. If we don't need multiple voices,
    // reduce the risk of crashes due to other apps speaking by setting the
    // priority to normal.
    if (GetPolicy().IsMultipleVoiceModeEnabled())
    {
        GetChannelInfo(AxSpeechMainChannel, false /* don't normalize */).
            m_textToSpeech.SetPriority(wxTTS_Over);
        GetChannelInfo(AxSpeechContentChannel, false /* don't normalize */).
            m_textToSpeech.SetPriority(wxTTS_Over);
    }
    else
    {
        GetChannelInfo(AxSpeechMainChannel, false /* don't normalize */).
            m_textToSpeech.SetPriority(wxTTS_Normal);
        GetChannelInfo(AxSpeechContentChannel, false /* don't normalize */).
            m_textToSpeech.SetPriority(wxTTS_Normal);
    }
}

// Save speech settings to config
bool AxSpeech::SaveSpeechSettings(wxConfigBase& config)
{
    AxSpeechChannel channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        wxString channelName;
        if (channel == AxSpeechMainChannel)
            channelName = wxT("SpeechMain");
        else
            channelName = wxT("SpeechContent");

        AxSpeechInfo& info = GetChannelInfo(channel);
        
        config.Write(channelName + wxT("/Speed"), info.m_speechSettingsInfo.m_speed);
        config.Write(channelName + wxT("/Volume"), info.m_speechSettingsInfo.m_volume);
        config.Write(channelName + wxT("/Pitch"), info.m_speechSettingsInfo.m_pitch);
        config.Write(channelName + wxT("/Engine"), info.m_speechSettingsInfo.m_engine);
        config.Write(channelName + wxT("/TestText"), info.m_speechSettingsInfo.m_testText);
        
        size_t i;
        for (i = 0; i < (size_t) info.m_textToSpeech.GetHandlerCount(); i++)
        {
            wxTTSHandler* handler = info.m_textToSpeech.GetHandler(i);
            
            config.Write(channelName + wxT("/") + handler->GetId() + wxT("Voice"), handler->GetVoice());
            config.Write(channelName + wxT("/") + handler->GetId() + wxT("VoiceVariant"), handler->GetVoiceVariant());
            config.Write(channelName + wxT("/") + handler->GetId() + wxT("ProgramLocation"), handler->GetProgramLocation());
            config.Write(channelName + wxT("/") + handler->GetId() + wxT("DataLocation"), handler->GetDataLocation());
        }
    }
        
    return true;
}

// Are the currently selected voices compatible?
bool AxSpeech::AreVoicesCompatible() const
{
    // Don't normalize the channel number if we really want to change the second voice settings.
    bool normalizeChannel = false;

    const wxTextToSpeech& speech1 = GetTextToSpeech(AxSpeechMainChannel, normalizeChannel);
    const wxTextToSpeech& speech2 = GetTextToSpeech(AxSpeechContentChannel, normalizeChannel);

    wxString voice1 = speech1.GetVoice();
    wxString voice2 = speech2.GetVoice();

    wxString engine1 = speech1.GetEngine();
    wxString engine2 = speech2.GetEngine();

    if ((engine1 == engine2) && !speech1.VoicesAreCompatible(voice1, voice2))
        return false;
    else
        return true;
}

// Get the speech info for the given channel
const AxSpeechInfo& AxSpeech::GetChannelInfo(AxSpeechChannel channel, bool normalize) const
{
    if (normalize)
        channel = NormalizeChannel(channel);

    if (channel == AxSpeechContentChannel)
        return m_contentSpeechChannel;
    else
        return m_mainSpeechChannel;
}

// Get the speech info for the given channel
AxSpeechInfo& AxSpeech::GetChannelInfo(AxSpeechChannel channel, bool normalize)
{
    if (normalize)
        channel = NormalizeChannel(channel);

    if (channel == AxSpeechContentChannel)
        return m_contentSpeechChannel;
    else
        return m_mainSpeechChannel;
}

// Normalize the channel, i.e. convert it to a usable channel taking the multiple voice mode setting into account.
AxSpeechChannel AxSpeech::NormalizeChannel(AxSpeechChannel channel) const
{
    if (channel == AxSpeechDefaultChannel)
        channel = AxSpeechMainChannel;
    if (channel == AxSpeechContentChannel && !GetPolicy().IsMultipleVoiceModeEnabled())
        channel = AxSpeechMainChannel;
    return channel;
}


// Returns true if frozen, that is, awaiting Thaw call before speaking.
bool AxSpeech::IsFrozen(AxSpeechChannel channel) const
{
    return (GetChannelInfo(channel).m_freezeCount > 0);
}

// Freeze - don't speak between Freeze/Thaw calls.
bool AxSpeech::Freeze(AxSpeechChannel channel)
{
    GetChannelInfo(channel).m_freezeCount ++;

    return true;
}

// Thaw - speak if freeze count is zero.
bool AxSpeech::Thaw(AxSpeechChannel channel)
{
    AxSpeechInfo& info = GetChannelInfo(channel);

    wxASSERT(info.m_freezeCount > 0);

    if (info.m_freezeCount > 0)
    {
        info.m_freezeCount --;
    }
    else
        return false;

    if (info.m_freezeCount == 0)
    {
        Speak(info.m_currentText, channel);
        info.m_currentText = wxEmptyString;
    }

    return true;
}

// Speak text if not frozen. Otherwise, append to stored
// text awaiting a Thaw call.
bool AxSpeech::Speak(const wxString& text, AxSpeechChannel channel)
{
    AxSpeechInfo& info = GetChannelInfo(channel);
    if (IsFrozen(channel))
    {
        info.m_currentText += wxT("\n");
        info.m_currentText += text;
    }
    else
    {
        if (!text.IsEmpty())
        {
            wxLogNull logNull;
            return info.m_textToSpeech.Speak(text);
        }
    }

    return true;
}

// Add to pending speech events
void AxSpeech::ScheduleSpeechEvent(AxSpeechEvent& event)
{
    AxSpeechInfo& info = GetChannelInfo(event.GetChannel());

    if (event.GetVerbosityPolicy() == -1)
        event.SetVerbosityPolicy(GetPolicy().GetVerbosityLevel());

    info.m_speechCounter ++ ;

    // FIXME: Problem with this is that the menu and menu items
    // may be deleted before they are used.
    // Could arrange to schedule menu items
    // in idle time, after speech events are processed.
    // Or, clone the menu/item but that could could clone a rather large
    // hierarchy.
    if (event.GetPurgeSpeech())
        PurgeSpeechEvents(event.GetChannel());
    else if (event.GetPurgeAllSpeech())
        PurgeSpeechEvents(AxSpeechAllChannels);

    info.m_speechEvents.Add((AxSpeechEvent*) event.Clone());

    if (event.GetFlags() & AxSpeechFlagsImmediate)
        ProcessPendingSpeechEvents();
}

// Clear pending speech events
void AxSpeech::ClearSpeechEvents(AxSpeechChannel channel)
{
    if (channel == AxSpeechAllChannels)
    {
        AxSpeechChannel ch;
        for (ch = AxSpeechMainChannel; ch <= AxSpeechContentChannel; ch ++)
        {
            ClearSpeechEvents(ch);
        }
    }
    else
    {
        AxSpeechInfo& info = GetChannelInfo(channel);
        size_t i;
        for (i = 0; i < info.m_speechEvents.GetCount(); i++)
        {
            AxSpeechEvent* event = info.m_speechEvents[i];
            delete event;
        }
        info.m_speechEvents.Clear();
    }
}

// Purge pending speech events
void AxSpeech::PurgeSpeechEvents(AxSpeechChannel channel)
{
    if (channel == AxSpeechAllChannels)
    {
        AxSpeechChannel ch;
        for (ch = AxSpeechMainChannel; ch <= AxSpeechContentChannel; ch ++)
        {
            PurgeSpeechEvents(ch);
        }
    }
    else
    {
        AxSpeechInfo& info = GetChannelInfo(channel);
        size_t i = 0;
        while (i < info.m_speechEvents.GetCount())
        {
            AxSpeechEvent* event = info.m_speechEvents[i];
            if ((event->GetFlags() & AxSpeechFlagsInviolate) == 0)
            {
                delete event;
                info.m_speechEvents.RemoveAt(i);
            }
            else
            {
                i ++;
            }
        }
    }
}

// Process any pending speech events
void AxSpeech::ProcessPendingSpeechEvents()
{
    AxSpeechChannel channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        AxSpeechInfo& info = GetChannelInfo(channel);
        if (info.m_speechEvents.GetCount() > 0)
        {
            Freeze(channel);
            
            size_t i;
            for (i = 0; i < info.m_speechEvents.GetCount(); i++)
            {
                AxSpeechEvent* event = info.m_speechEvents[i];
                ProcessSpeechEvent(* event);
            }
            
            ClearSpeechEvents(channel);
            
            Thaw(channel);
        }
    }
}

// Is this punctuation?
bool AxSpeechEventHandler::IsPunctuation(const wxChar& ch)
{
    return (ch == wxT('.') || ch == wxT(';') || ch == wxT(':') ||
            ch == wxT(',') || ch == wxT('!') || ch == wxT('?'));
}

// Remove trailing punctuation.
bool AxSpeechEventHandler::RemoveTrailingPunctuation(wxString& str)
{
    if (str.Length() > 0 && IsPunctuation(str.Last()))
        str = str.Mid(0, str.Length()-1);
    return true;
}

// Add a full stop to the string, unless there's already one there.
// Remove extra punctuation, e.g. semicolon.
bool AxSpeechEventHandler::AddStop(wxString& str, const wxString& punct)
{
    if (str.Length() > 0)
    {
        RemoveTrailingPunctuation(str);
        str += punct;
        // This tells some engines to force a pause
        str += wxT("\n");
    }
    return true;
}

// Add a semicolon to the string to create a pause, unless there's already one there.
// Remove extra punctuation.
bool AxSpeechEventHandler::AddPause(wxString& str, const wxString& punct)
{
    AddStop(str, punct);
    return true;
}

bool AxStandardSpeechEventHandler::ProcessSpeechEvent(AxSpeech* speech, AxSpeechEvent& event)
{
    if (!speech->GetPolicy().IsSpeechEnabled())
        return true;

    int verbosityPolicy = event.GetVerbosityPolicy();

    if (event.GetVerbosityLevel() > verbosityPolicy)
        return true;

    AxSpeechChannel channel = speech->NormalizeChannel(event.GetChannel());

    wxString eventType = event.GetSpeechEventType();
    wxString toSpeak;
    bool useDescription = verbosityPolicy >= AxSpeechVerbosityHigh;
    if (eventType == AxSpeechEventText)
    {
        if (!event.GetText().IsEmpty())
            toSpeak += event.GetText();
    }
    else if (eventType == AxSpeechEventActivateItem)
    {
        if (verbosityPolicy < AxSpeechVerbosityMedium)
            return true;

        if (event.GetMenuItem())
        {
            wxString kind = event.GetMenuItem()->GetKind();

            toSpeak += _("Activating ");
            toSpeak += wxString::Format(wxT("%d, "), event.GetMenuItem()->GetPosition()+1);
            if (!kind.IsEmpty())
            {
                toSpeak += kind;
                toSpeak += wxT(", ");
            }
            toSpeak += event.GetMenuItem()->GetLabel();
            AddStop(toSpeak);
            if (useDescription && !event.GetMenuItem()->GetDescription().IsEmpty())
            {
                toSpeak += wxT(" ");
                toSpeak += event.GetMenuItem()->GetDescription();
                AddStop(toSpeak);
            }
        }
    }
    else if (eventType == AxSpeechEventRefreshMenu ||
             eventType == AxSpeechEventInitialView ||
             eventType == AxSpeechEventDescribeContext)
    {
        if (!event.GetMenu())
            return false;

        wxString menuIntro;
        
        if (eventType == AxSpeechEventRefreshMenu || eventType == AxSpeechEventDescribeContext)
        {
            if (event.GetNavigationHint() == AxNavigationHintBack)
            {
                toSpeak += _("Back to menu ");
                toSpeak += event.GetMenu()->GetLabel();
            }
            else if (event.GetNavigationHint() == AxNavigationHintUp)
            {
                toSpeak += _("Up to menu ");
                toSpeak += event.GetMenu()->GetLabel();
            }                
            else
            {
                toSpeak += _("Menu ");
                toSpeak += event.GetMenu()->GetLabel();
            }
            AddPause(toSpeak);
        }
        else if (eventType == AxSpeechEventInitialView)
        {
            toSpeak += _("Home menu is ");
            toSpeak += event.GetMenu()->GetLabel();
            AddPause(toSpeak);
        }
        
        if (verbosityPolicy < AxSpeechVerbosityLow)
            return true;

        // When we've already visited a menu, we may not need to voice it all
        // over again (unless requested), so apply some hint knowledge.
        bool onlyNeedMenuName = 
            ((event.GetNavigationHint() == AxNavigationHintBack) ||
             (event.GetNavigationHint() == AxNavigationHintUp));

        if ((verbosityPolicy <= AxSpeechVerbosityLow) ||
            ((verbosityPolicy == AxSpeechVerbosityMedium) &&
            onlyNeedMenuName))
        {
            // Output the selected item
            AxMenuItem* selectedItem = event.GetMenu()->GetCurrentItem();
            if (selectedItem)
            {
                wxString kind = selectedItem->GetKind();

                toSpeak += _(" The selection is ");
                toSpeak += wxString::Format(wxT("%d, "), selectedItem->GetPosition()+1);

                if (!kind.IsEmpty())
                {
                    toSpeak += kind;
                    toSpeak += wxT(", ");
                }

                toSpeak += event.GetMenuItem()->GetLabel();
                AddStop(toSpeak);
            }

            // Just output what we have so far
        }
        else // more verbose
        {
            toSpeak += wxT(" ");
            if (event.GetMenu()->GetCount() == 1)
                toSpeak += wxString::Format(_("There is 1 item: \n"));
            else
                toSpeak += wxString::Format(_("There are %d items: \n"), (int) event.GetMenu()->GetCount());
            size_t i;
            for (i = 0; i < event.GetMenu()->GetCount(); i++)
            {
                toSpeak += wxString::Format(wxT(" Item %d, "), (int) i+1);
                AxMenuItem* item = event.GetMenu()->GetItem(i);

                wxString kind = item->GetKind();
                if (!kind.IsEmpty())
                {
                    toSpeak += kind;
                    toSpeak += wxT(", ");
                }

                toSpeak += item->GetLabel();
                RemoveTrailingPunctuation(toSpeak);

                if (!item->IsEnabled())
                    toSpeak += _(" (disabled)");
                if (event.GetMenu()->GetCurrentSelection() == (int) i)
                    toSpeak += _(" (selected)");
                AddStop(toSpeak);
                if (useDescription)
                {
                    toSpeak += wxT(" ");
                    toSpeak += item->GetDescription();
                    AddStop(toSpeak);
                }
            }
        }
    }
    else if (eventType == AxSpeechEventChangeSelection || eventType == AxSpeechEventDescribeItem)
    {
        if (verbosityPolicy < AxSpeechVerbosityLow)
            return true;

        if (event.GetMenuItem())
        {
            wxString kind = event.GetMenuItem()->GetKind();

            if (eventType == AxSpeechEventDescribeItem)
                toSpeak += _("Item ");
            else
                toSpeak += _("Selecting ");
            toSpeak += wxString::Format(wxT("%d, "), event.GetMenuItem()->GetPosition()+1);
            if (!kind.IsEmpty())
            {
                toSpeak += kind;
                toSpeak += wxT(", ");
            }

            toSpeak += event.GetMenuItem()->GetLabel();
            AddStop(toSpeak);
            if (useDescription)
            {
                toSpeak += wxT(" ");
                toSpeak += event.GetMenuItem()->GetDescription();
                AddStop(toSpeak);
            }
        }
    }
    else if (eventType == AxSpeechEventToggleSelection)
    {
        if (verbosityPolicy < AxSpeechVerbosityLow)
            return true;

        if (event.GetMenuItem())
        {
            toSpeak += _("Toggled ");
            toSpeak += wxString::Format(wxT("%d, "), event.GetMenuItem()->GetPosition()+1);
            toSpeak += event.GetMenuItem()->GetLabel();
            AddStop(toSpeak);
            if (useDescription)
            {
                toSpeak += wxT(" ");
                toSpeak += event.GetMenuItem()->GetDescription();
                AddStop(toSpeak);
            }
        }
    }
    else if (eventType == AxSpeechEventChangeValue)
    {
        if (verbosityPolicy < AxSpeechVerbosityLow)
            return true;

    }
    else if (eventType == AxSpeechEventItemLabelChanged)
    {
        if (verbosityPolicy < AxSpeechVerbosityMedium)
            return true;

        if (event.GetMenuItem())
        {
            toSpeak += _("Item changed: ");
            toSpeak += wxString::Format(wxT("%d, "), event.GetMenuItem()->GetPosition()+1);
            toSpeak += event.GetMenuItem()->GetLabel();
            AddStop(toSpeak);
        }
    }

    if (!toSpeak.IsEmpty())
    {
        speech->Speak(toSpeak, channel);
    }

    return false;
}

IMPLEMENT_DYNAMIC_CLASS( AxSpeechPolicy, wxObject )

void AxSpeechPolicy::Init()
{
    m_enableSpeech = false;
    m_verbosityLevel = AxSpeechVerbosityMedium;
    m_enableMultipleVoiceMode = true;
}

void AxSpeechPolicy::Copy(const AxSpeechPolicy& policy)
{
    m_enableSpeech = policy.m_enableSpeech;
    m_verbosityLevel = policy.m_verbosityLevel;
    m_enableMultipleVoiceMode = policy.m_enableMultipleVoiceMode;
}

IMPLEMENT_CLASS( AxSpeechEvent, wxNotifyEvent )

void AxSpeechEvent::Init()
{
    m_verbosityLevel = 0;
    m_verbosityPolicy = -1;
    m_menu = NULL;
    m_menuItem = NULL;
    m_navigationHint = AxNavigationHintNone;
    m_flags = 0;
}

void AxSpeechEvent::Copy(const AxSpeechEvent& event)
{
    m_speechEventType = event.m_speechEventType;
    m_text = event.m_text;
    m_verbosityLevel = event.m_verbosityLevel;
    m_verbosityPolicy = event.m_verbosityPolicy;
    m_menu = event.m_menu;
    m_menuItem = event.m_menuItem;
    m_navigationHint = event.m_navigationHint;
    m_flags = event.m_flags;
}
